package com.knife.changerecord.infra.utils;

import cn.hutool.core.date.DateUtil;
import com.knife.changerecord.config.ChangeRecordThreadPool;
import com.knife.changerecord.domain.ChangeRecordTxHeader;
import com.knife.changerecord.domain.ChangeRecordTxLine;
import com.knife.changerecord.exception.ChangeRecordException;
import com.knife.changerecord.infra.annotations.ChangeRecordField;
import com.knife.changerecord.infra.mapper.ChangeRecordTxHeaderMapper;
import com.knife.changerecord.infra.mapper.ChangeRecordTxLineMapper;
import com.knife.common.constants.BaseConstants;
import com.knife.lov.domain.service.impl.KnifeLovValueHelper;
import com.knife.oauth.util.DetailHelper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.annotation.Resource;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * @author lth
 * @created 2025年08月19日 星期二 16:20
 */
@Component
@SuppressWarnings("ALL")
@Slf4j
public class KnifeChangeRecordUtils {
    @Resource
    private KnifeLovValueHelper knifeLovValueHelper;
    @Resource
    private ChangeRecordTxHeaderMapper changeRecordTxHeaderMapper;
    @Resource
    private ChangeRecordTxLineMapper changeRecordTxLineMapper;

    private static final DateTimeFormatter DATETIME_FORMATTER =
            DateTimeFormatter.ofPattern(BaseConstants.Pattern.DATETIME);

    private static final Map<Class<?>, Function<Object, String>> CONVERTERS = initConverters();

    @Transactional
    public <T> void createChangeRecord(Long tenantId, String moduleType, Long moduleId,
                                       String operationType, T oldObj, T newObj) {
        // 获取当前线程下用户的信息
        RequestAttributes context = RequestContextHolder.currentRequestAttributes();
        SecurityContext securityContext = SecurityContextHolder.getContext();

        ThreadPoolExecutor changeRecordThreadPool = ChangeRecordThreadPool.getChangeRecordThreadPool();
        changeRecordThreadPool.execute(()->{
            try {
                // 设置当前线程下用户的信息
                RequestContextHolder.setRequestAttributes(context);
                SecurityContextHolder.setContext(securityContext);

                Class<?> aClass = getClass(oldObj, newObj);
                List<Field> fields = getAnnotatedFields(aClass);
                if (CollectionUtils.isEmpty(fields)) {
                    log.debug("No fields annotated with @ChangeRecordField found for {}", aClass);
                    return;
                }

                processLovValues(oldObj, newObj);
                ChangeRecordTxHeader header = createAndSaveHeader(tenantId, moduleType, moduleId, operationType);
                List<ChangeRecordTxLine> lines = buildChangeLines(fields, oldObj, newObj, header.getId(), tenantId);
                saveChangeLines(lines);
            } catch (IllegalAccessException e) {
                log.error("Failed to create change record for {}[{}]", moduleType, moduleId, e);
                throw new ChangeRecordException("Failed to create change record", e);
            } finally {
                SecurityContextHolder.clearContext();
                RequestContextHolder.resetRequestAttributes();
            }
        });
    }

    private <T> Class<?> getClass(T oldObj, T newObj) {
        if (Objects.nonNull(oldObj)) {
            return oldObj.getClass();
        } else {
            return newObj.getClass();
        }
    }

    private List<Field> getAnnotatedFields(Class<?> clazz) {
        return FieldUtils.getFieldsListWithAnnotation(clazz, ChangeRecordField.class);
    }

    private <T> void processLovValues(T oldObj, T newObj) {
        if (Objects.nonNull(oldObj)) {
            knifeLovValueHelper.lovProcessTranslateWithClearCache(oldObj);
        }
        if (Objects.nonNull(newObj)) {
            knifeLovValueHelper.lovProcessTranslateWithClearCache(newObj);
        }
    }

    private ChangeRecordTxHeader createAndSaveHeader(Long tenantId, String moduleType,
                                                     Long moduleId, String operationType) {
        ChangeRecordTxHeader header = new ChangeRecordTxHeader(moduleType, moduleId, operationType);
        header.setTenantId(tenantId);
        header.setOperationTime(new Date());
        header.setOperatorId(DetailHelper.getUserDetails().getUserId());
        changeRecordTxHeaderMapper.insert(header);
        log.debug("Created change record header with ID: {}", header.getId());
        return header;
    }

    private <T> List<ChangeRecordTxLine> buildChangeLines(List<Field> fields, T oldObj, T newObj,
                                                          Long headerId, Long tenantId)
            throws IllegalAccessException {
        List<ChangeRecordTxLine> lines = new ArrayList<>();

        for (Field field : fields) {
            try {
                field.setAccessible(true);
                ChangeRecordField annotation = field.getAnnotation(ChangeRecordField.class);

                Object oldValue = Objects.nonNull(oldObj) ? FieldUtils.readField(field, oldObj) : null;
                Object newValue = FieldUtils.readField(field, newObj);

                if (!Objects.equals(oldValue,newValue)){
                    ChangeRecordTxLine line = createChangeLine(field, oldObj, newObj,
                            oldValue, newValue,
                            annotation, headerId, tenantId);
                    lines.add(line);
                }
            } catch (IllegalAccessException e) {
                log.warn("Failed to compare field {} of {}", field.getName(), newObj.getClass(), e);
                throw e;
            }
        }

        return lines;
    }

    private boolean isSignificantChange(Object oldValue, Object newValue) {
        // 实现自定义的显著变化判断逻辑
        return !Objects.equals(oldValue, newValue);
    }

    private ChangeRecordTxLine createChangeLine(Field field, Object oldObj, Object newObj,
                                                Object oldValue, Object newValue,
                                                ChangeRecordField annotation,
                                                Long headerId, Long tenantId)
            throws IllegalAccessException {
        ChangeRecordTxLine line = new ChangeRecordTxLine();
        line.setTxHeaderId(headerId);
        line.setFieldName(annotation.fieldName());
        line.setTenantId(tenantId);

        // 设置旧值
        setFieldValues(line::setOldValue, line::setOldValueMeaning,
                oldValue, oldObj, annotation.valueMeaningField());

        // 设置新值
        setFieldValues(line::setNewValue, line::setNewValueMeaning,
                newValue, newObj, annotation.valueMeaningField());

        return line;
    }

    private void setFieldValues(Consumer<String> valueSetter, Consumer<String> meaningSetter,
                                Object value, Object obj, String meaningField)
            throws IllegalAccessException {
        if (value == null) {
            valueSetter.accept(null);
            meaningSetter.accept(null);
        } else {
            valueSetter.accept(convert(value));

            if (StringUtils.isNotEmpty(meaningField) &&  Objects.nonNull(obj)) {
                Object meaning = FieldUtils.readField(obj, meaningField, true);
                meaningSetter.accept(meaning != null ? meaning.toString() : null);
            }
        }
    }

    private void saveChangeLines(List<ChangeRecordTxLine> lines) {
        if (CollectionUtils.isNotEmpty(lines)) {
            for (ChangeRecordTxLine line : lines) {
                changeRecordTxLineMapper.insert(line);
            }
            log.debug("Saved {} change record lines", lines.size());
        }
    }

    private String convert(Object objValue) {
        if (objValue == null) return null;

        return Optional.ofNullable(CONVERTERS.get(objValue.getClass()))
                .orElseGet(() -> {
                    return Object::toString;
                })
                .apply(objValue);
    }

    private static Map<Class<?>, Function<Object, String>> initConverters() {
        Map<Class<?>, Function<Object, String>> converters = new HashMap<>();
        converters.put(Boolean.class, Object::toString);
        converters.put(Integer.class, Object::toString);
        converters.put(Long.class, Object::toString);
        converters.put(Double.class, Object::toString);
        converters.put(BigDecimal.class, Object::toString);
        converters.put(Date.class, v -> DateUtil.format((Date) v, BaseConstants.Pattern.DATETIME));
        converters.put(LocalDateTime.class, v -> DATETIME_FORMATTER.format((LocalDateTime) v));
        converters.put(LocalDate.class, v -> ((LocalDate) v).format(DateTimeFormatter.ISO_LOCAL_DATE));
        return converters;
    }
}
